/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.xml.bind.util;

import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.XMLFilterImpl;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.transform.sax.SAXSource;
import org.xml.sax.XMLFilter;

/**
 * JAXP {@link javax.xml.transform.Source} implementation
 * that marshals a JAXB-generated object.
 *
 * <p>
 * This utility class is useful to combine JAXB with
 * other Java/XML technologies.
 *
 * <p>
 * The following example shows how to use JAXB to marshal a document
 * for transformation by XSLT.
 *
 * <blockquote>
 * <pre>
 *       MyObject o = // get JAXB content tree
 *
 *       // jaxbContext is a JAXBContext object from which 'o' is created.
 *       JAXBSource source = new JAXBSource( jaxbContext, o );
 *
 *       // set up XSLT transformation
 *       TransformerFactory tf = TransformerFactory.newInstance();
 *       Transformer t = tf.newTransformer(new StreamSource("test.xsl"));
 *
 *       // run transformation
 *       t.transform(source,new StreamResult(System.out));
 *    </pre>
 * </blockquote>
 *
 * <p>
 * The fact that JAXBSource derives from SAXSource is an implementation
 * detail. Thus in general applications are strongly discouraged from
 * accessing methods defined on SAXSource. In particular,
 * the setXMLReader and setInputSource methods shall never be called.
 * The XMLReader object obtained by the getXMLReader method shall
 * be used only for parsing the InputSource object returned by
 * the getInputSource method.
 *
 * <p>
 * Similarly the InputSource object obtained by the getInputSource
 * method shall be used only for being parsed by the XMLReader object
 * returned by the getXMLReader.
 *
 * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
 */
public class JAXBSource extends SAXSource {

  /**
   * Creates a new {@link javax.xml.transform.Source} for the given content object.
   *
   * @param context JAXBContext that was used to create <code>contentObject</code>. This context is
   * used to create a new instance of marshaller and must not be null.
   * @param contentObject An instance of a JAXB-generated class, which will be used as a {@link
   * javax.xml.transform.Source} (by marshalling it into XML).  It must not be null.
   * @throws JAXBException if an error is encountered while creating the JAXBSource or if either of
   * the parameters are null.
   */
  public JAXBSource(JAXBContext context, Object contentObject)
      throws JAXBException {

    this(
        (context == null) ?
            assertionFailed(Messages.format(Messages.SOURCE_NULL_CONTEXT)) :
            context.createMarshaller(),

        (contentObject == null) ?
            assertionFailed(Messages.format(Messages.SOURCE_NULL_CONTENT)) :
            contentObject);
  }

  /**
   * Creates a new {@link javax.xml.transform.Source} for the given content object.
   *
   * @param marshaller A marshaller instance that will be used to marshal <code>contentObject</code>
   * into XML. This must be created from a JAXBContext that was used to build
   * <code>contentObject</code> and must not be null.
   * @param contentObject An instance of a JAXB-generated class, which will be used as a {@link
   * javax.xml.transform.Source} (by marshalling it into XML).  It must not be null.
   * @throws JAXBException if an error is encountered while creating the JAXBSource or if either of
   * the parameters are null.
   */
  public JAXBSource(Marshaller marshaller, Object contentObject)
      throws JAXBException {

    if (marshaller == null) {
      throw new JAXBException(
          Messages.format(Messages.SOURCE_NULL_MARSHALLER));
    }

    if (contentObject == null) {
      throw new JAXBException(
          Messages.format(Messages.SOURCE_NULL_CONTENT));
    }

    this.marshaller = marshaller;
    this.contentObject = contentObject;

    super.setXMLReader(pseudoParser);
    // pass a dummy InputSource. We don't care
    super.setInputSource(new InputSource());
  }

  private final Marshaller marshaller;
  private final Object contentObject;

  // this object will pretend as an XMLReader.
  // no matter what parameter is specified to the parse method,
  // it just parse the contentObject.
  private final XMLReader pseudoParser = new XMLReader() {
    public boolean getFeature(String name) throws SAXNotRecognizedException {
      if (name.equals("http://xml.org/sax/features/namespaces")) {
        return true;
      }
      if (name.equals("http://xml.org/sax/features/namespace-prefixes")) {
        return false;
      }
      throw new SAXNotRecognizedException(name);
    }

    public void setFeature(String name, boolean value) throws SAXNotRecognizedException {
      if (name.equals("http://xml.org/sax/features/namespaces") && value) {
        return;
      }
      if (name.equals("http://xml.org/sax/features/namespace-prefixes") && !value) {
        return;
      }
      throw new SAXNotRecognizedException(name);
    }

    public Object getProperty(String name) throws SAXNotRecognizedException {
      if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
        return lexicalHandler;
      }
      throw new SAXNotRecognizedException(name);
    }

    public void setProperty(String name, Object value) throws SAXNotRecognizedException {
      if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
        this.lexicalHandler = (LexicalHandler) value;
        return;
      }
      throw new SAXNotRecognizedException(name);
    }

    private LexicalHandler lexicalHandler;

    // we will store this value but never use it by ourselves.
    private EntityResolver entityResolver;

    public void setEntityResolver(EntityResolver resolver) {
      this.entityResolver = resolver;
    }

    public EntityResolver getEntityResolver() {
      return entityResolver;
    }

    private DTDHandler dtdHandler;

    public void setDTDHandler(DTDHandler handler) {
      this.dtdHandler = handler;
    }

    public DTDHandler getDTDHandler() {
      return dtdHandler;
    }

    // SAX allows ContentHandler to be changed during the parsing,
    // but JAXB doesn't. So this repeater will sit between those
    // two components.
    private XMLFilter repeater = new XMLFilterImpl();

    public void setContentHandler(ContentHandler handler) {
      repeater.setContentHandler(handler);
    }

    public ContentHandler getContentHandler() {
      return repeater.getContentHandler();
    }

    private ErrorHandler errorHandler;

    public void setErrorHandler(ErrorHandler handler) {
      this.errorHandler = handler;
    }

    public ErrorHandler getErrorHandler() {
      return errorHandler;
    }

    public void parse(InputSource input) throws SAXException {
      parse();
    }

    public void parse(String systemId) throws SAXException {
      parse();
    }

    public void parse() throws SAXException {
      // parses a content object by using the given marshaller
      // SAX events will be sent to the repeater, and the repeater
      // will further forward it to an appropriate component.
      try {
        marshaller.marshal(contentObject, (XMLFilterImpl) repeater);
      } catch (JAXBException e) {
        // wrap it to a SAXException
        SAXParseException se =
            new SAXParseException(e.getMessage(),
                null, null, -1, -1, e);

        // if the consumer sets an error handler, it is our responsibility
        // to notify it.
        if (errorHandler != null) {
          errorHandler.fatalError(se);
        }

        // this is a fatal error. Even if the error handler
        // returns, we will abort anyway.
        throw se;
      }
    }
  };

  /**
   * Hook to throw exception from the middle of a contructor chained call
   * to this
   */
  private static Marshaller assertionFailed(String message)
      throws JAXBException {

    throw new JAXBException(message);
  }
}
